package com.itcoon.transform.starter.context;

import com.itcoon.transform.base.annotation.AssembleModel;
import com.itcoon.transform.base.pool.AssembleBeanHolder;
import com.itcoon.transform.starter.context.annotation.EnableTransform4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class AssembleScannerRegister implements ImportBeanDefinitionRegistrar {

    private static final Logger LOGGER = LoggerFactory.getLogger(AssembleScannerRegister.class);

    private static final String CLASS_SUFFIX = ".class";
    private static final String FILE_PROTOCAL = "file";
    private static final String JAR_PROTOCAL = "jar";

    public AssembleScannerRegister() {
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes dtoMappingAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(EnableTransform4j.class.getName()));
        if (dtoMappingAttrs != null) {
            AnnotationAttributes[] dtoMappingAttributes = (AnnotationAttributes[]) dtoMappingAttrs.get("groups");
            if (dtoMappingAttributes != null && dtoMappingAttributes.length > 0) {
                for (AnnotationAttributes dtoMappingAttribute : dtoMappingAttributes) {
                    String basePackage = dtoMappingAttribute.getString("basePackage");
                    boolean isRecursion = dtoMappingAttribute.getBoolean("isRecursion");
                    registerDtoDefinitions(basePackage, isRecursion);
                }
            }
        }
    }

    private void registerDtoDefinitions(String basePackage, boolean isRecursion) {
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        Set<String> classNames = new HashSet<>();
        String packagePath = basePackage.replace(".", "/");
        URL url = contextClassLoader.getResource(packagePath);
        if (url != null) {
            String protocol = url.getProtocol();
            if (protocol.equals(FILE_PROTOCAL)) {
                Set<String> dirClassNames = getClassNameFromDir(url.getPath(), basePackage, isRecursion, CLASS_SUFFIX);
                classNames.addAll(dirClassNames);
            } else if (protocol.equals(JAR_PROTOCAL)) {
                JarFile jarFile = null;
                try {
                    jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (jarFile != null) {
                    Set<String> dirClassNames = getClassNameFromJar(jarFile.entries(), basePackage);
                    classNames.addAll(dirClassNames);
                }
            }
            Set<Class<?>> dtoClasses = filterByAnnotation(classNames);
            AssembleBeanHolder.getInstance().addTargetClasses(dtoClasses);
        } else {
            LOGGER.error("[Transform4j Warning] - BasePackages must be specified and contains targetClasses! The incorrect package is " + basePackage);
        }
    }


    private Set<Class<?>> filterByAnnotation(Collection<String> classNames) {
        Assert.notNull(classNames, "[Assertion Failed] - classNames must be not null!");
        Set<Class<?>> set = new HashSet<>();
        try {
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);
                if (clazz.getAnnotation(AssembleModel.class) != null) {
                    set.add(clazz);
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return set;
    }

    private Set<String> getClassNameFromJar(Enumeration<JarEntry> jarEntries, String basePackage) {
        Set<String> classNames = new HashSet<>();
        while (jarEntries.hasMoreElements()) {
            JarEntry entry = jarEntries.nextElement();
            if (!entry.isDirectory()) {
                String entryName = entry.getName().replace("/", ".");
                if (entryName.endsWith(CLASS_SUFFIX) && !entryName.contains("$") && entryName.startsWith(basePackage)) {
                    entryName = entryName.replace(".class", "");
                    classNames.add(entryName);
                }
            }
        }
        return classNames;
    }

    private Set<String> getClassNameFromDir(String filePath, String basePackage, boolean isRecursion, String suffixstr) {
        Set<String> classNames = new HashSet<>();
        try {
            filePath = URLDecoder.decode(new String(filePath.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8), "UTF-8");
            File file = new File(filePath);
            File[] files = file.listFiles();
            if (files == null) {
                return classNames;
            }
            for (File childFile : files) {
                if (childFile.isDirectory()) {
                    if (isRecursion) {
                        classNames.addAll(getClassNameFromDir(childFile.getPath(), basePackage + "." + childFile.getName(), true, suffixstr));
                    }
                } else {
                    String fileName = childFile.getName();
                    if (fileName.endsWith(suffixstr) && !fileName.contains("$")) {
                        fileName = fileName.replace(suffixstr, "");
                        classNames.add(basePackage + "." + fileName);
                    }
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return classNames;
    }

}